Explore a correspondência de padrões avançada em JavaScript usando expressões regulares. Aprenda sobre a sintaxe regex, aplicações práticas e técnicas de otimização para um código eficiente e robusto.
Correspondência de Padrões em JavaScript com Expressões Regulares: Um Guia Abrangente
Expressões regulares (regex) são uma ferramenta poderosa para correspondência de padrões e manipulação de texto em JavaScript. Elas permitem que os desenvolvedores pesquisem, validem e transformem strings com base em padrões definidos. Este guia oferece uma visão abrangente das expressões regulares em JavaScript, cobrindo sintaxe, uso e técnicas avançadas.
O que são Expressões Regulares?
Uma expressão regular é uma sequência de caracteres que define um padrão de busca. Esses padrões são usados para corresponder e manipular strings. As expressões regulares são amplamente utilizadas em programação para tarefas como:
- Validação de Dados: Garantir que a entrada do usuário esteja em conformidade com formatos específicos (ex: endereços de e-mail, números de telefone).
- Extração de Dados: Recuperar informações específicas de um texto (ex: extrair datas, URLs ou preços).
- Pesquisar e Substituir: Encontrar e substituir texto com base em padrões complexos.
- Processamento de Texto: Dividir, juntar ou transformar strings com base em regras definidas.
Criando Expressões Regulares em JavaScript
Em JavaScript, as expressões regulares podem ser criadas de duas maneiras:
- Usando um Literal de Expressão Regular: Coloque o padrão entre barras (
/). - Usando o Construtor
RegExp: Crie um objetoRegExpcom o padrão como uma string.
Exemplo:
// Usando um literal de expressão regular
const regexLiteral = /hello/;
// Usando o construtor RegExp
const regexConstructor = new RegExp("hello");
A escolha entre os dois métodos depende se o padrão é conhecido em tempo de compilação ou gerado dinamicamente. Use a notação literal quando o padrão for fixo e conhecido antecipadamente. Use o construtor quando o padrão precisar ser construído programaticamente, especialmente ao incorporar variáveis.
Sintaxe Básica de Regex
As expressões regulares consistem em caracteres que representam o padrão a ser correspondido. Aqui estão alguns componentes fundamentais de regex:
- Caracteres Literais: Correspondem aos próprios caracteres (ex:
/a/corresponde ao caractere 'a'). - Metacaracteres: Têm significados especiais (ex:
.,^,$,*,+,?,[],{},(),\,|). - Classes de Caracteres: Representam conjuntos de caracteres (ex:
[abc]corresponde a 'a', 'b' ou 'c'). - Quantificadores: Especificam quantas vezes um caractere ou grupo deve ocorrer (ex:
*,+,?,{n},{n,},{n,m}). - Âncoras: Correspondem a posições na string (ex:
^corresponde ao início,$corresponde ao fim).
Metacaracteres Comuns:
.(ponto): Corresponde a qualquer caractere único, exceto nova linha.^(acento circunflexo): Corresponde ao início da string.$(cifrão): Corresponde ao fim da string.*(asterisco): Corresponde a zero ou mais ocorrências do caractere ou grupo anterior.+(sinal de mais): Corresponde a uma ou mais ocorrências do caractere ou grupo anterior.?(ponto de interrogação): Corresponde a zero ou uma ocorrência do caractere ou grupo anterior. Usado para caracteres opcionais.[](colchetes): Define uma classe de caracteres, correspondendo a qualquer caractere único dentro dos colchetes.{}(chaves): Especifica o número de ocorrências a serem correspondidas.{n}corresponde exatamente n vezes,{n,}corresponde n ou mais vezes,{n,m}corresponde entre n e m vezes.()(parênteses): Agrupa caracteres e captura a substring correspondida.\(barra invertida): Escapa metacaracteres, permitindo que você os corresponda literalmente.|(pipe): Atua como um operador "ou", correspondendo à expressão antes ou depois dele.
Classes de Caracteres:
[abc]: Corresponde a qualquer um dos caracteres a, b ou c.[^abc]: Corresponde a qualquer caractere que *não* seja a, b ou c.[a-z]: Corresponde a qualquer letra minúscula de a a z.[A-Z]: Corresponde a qualquer letra maiúscula de A a Z.[0-9]: Corresponde a qualquer dígito de 0 a 9.[a-zA-Z0-9]: Corresponde a qualquer caractere alfanumérico.\d: Corresponde a qualquer dígito (equivalente a[0-9]).\D: Corresponde a qualquer caractere não-dígito (equivalente a[^0-9]).\w: Corresponde a qualquer caractere de palavra (alfanumérico mais sublinhado; equivalente a[a-zA-Z0-9_]).\W: Corresponde a qualquer caractere que não seja de palavra (equivalente a[^a-zA-Z0-9_]).\s: Corresponde a qualquer caractere de espaço em branco (espaço, tabulação, nova linha, etc.).\S: Corresponde a qualquer caractere que não seja espaço em branco.
Quantificadores:
*: Corresponde ao elemento anterior zero ou mais vezes. Por exemplo,a*corresponde a "", "a", "aa", "aaa", e assim por diante.+: Corresponde ao elemento anterior uma ou mais vezes. Por exemplo,a+corresponde a "a", "aa", "aaa", mas não a "".?: Corresponde ao elemento anterior zero ou uma vez. Por exemplo,a?corresponde a "" ou "a".{n}: Corresponde ao elemento anterior exatamente *n* vezes. Por exemplo,a{3}corresponde a "aaa".{n,}: Corresponde ao elemento anterior *n* ou mais vezes. Por exemplo,a{2,}corresponde a "aa", "aaa", "aaaa", e assim por diante.{n,m}: Corresponde ao elemento anterior entre *n* e *m* vezes (inclusive). Por exemplo,a{2,4}corresponde a "aa", "aaa", ou "aaaa".
Âncoras:
^: Corresponde ao início da string. Por exemplo,^Hellocorresponde a strings que *começam* com "Hello".$: Corresponde ao fim da string. Por exemplo,World$corresponde a strings que *terminam* com "World".\b: Corresponde a um limite de palavra. Esta é a posição entre um caractere de palavra (\w) e um caractere que não é de palavra (\W) ou o início ou fim da string. Por exemplo,\bword\bcorresponde à palavra inteira "word".
Flags:
As flags de regex modificam o comportamento das expressões regulares. Elas são anexadas ao final do literal da regex ou passadas como um segundo argumento para o construtor RegExp.
g(global): Corresponde a todas as ocorrências do padrão, não apenas à primeira.i(ignore case): Realiza a correspondência sem diferenciar maiúsculas de minúsculas.m(multiline): Habilita o modo multilinha, onde^e$correspondem ao início e fim de cada linha (separadas por\n).s(dotAll): Permite que o ponto (.) corresponda também a caracteres de nova linha.u(unicode): Habilita o suporte completo a Unicode.y(sticky): Corresponde apenas a partir do índice indicado pela propriedadelastIndexda regex.
Métodos Regex do JavaScript
O JavaScript fornece vários métodos para trabalhar com expressões regulares:
test(): Testa se uma string corresponde ao padrão. Retornatrueoufalse.exec(): Executa uma busca por uma correspondência em uma string. Retorna um array contendo o texto correspondido e os grupos capturados, ounullse nenhuma correspondência for encontrada.match(): Retorna um array contendo os resultados da correspondência de uma string com uma expressão regular. Comporta-se de maneira diferente com e sem a flagg.search(): Testa por uma correspondência em uma string. Retorna o índice da primeira correspondência, ou -1 se nenhuma correspondência for encontrada.replace(): Substitui ocorrências de um padrão por uma string de substituição ou uma função que retorna a string de substituição.split(): Divide uma string em um array de substrings com base em uma expressão regular.
Exemplos Usando Métodos Regex:
// test()
const regex = /hello/;
const str = "hello world";
console.log(regex.test(str)); // Saída: true
// exec()
const regex2 = /hello (\w+)/;
const str2 = "hello world";
const result = regex2.exec(str2);
console.log(result); // Saída: ["hello world", "world", index: 0, input: "hello world", groups: undefined]
// match() com a flag 'g'
const regex3 = /\d+/g; // Corresponde a um ou mais dígitos globalmente
const str3 = "There are 123 apples and 456 oranges.";
const matches = str3.match(regex3);
console.log(matches); // Saída: ["123", "456"]
// match() sem a flag 'g'
const regex4 = /\d+/;
const str4 = "There are 123 apples and 456 oranges.";
const match = str4.match(regex4);
console.log(match); // Saída: ["123", index: 11, input: "There are 123 apples and 456 oranges.", groups: undefined]
// search()
const regex5 = /world/;
const str5 = "hello world";
console.log(str5.search(regex5)); // Saída: 6
// replace()
const regex6 = /world/;
const str6 = "hello world";
const newStr = str6.replace(regex6, "JavaScript");
console.log(newStr); // Saída: hello JavaScript
// replace() com uma função
const regex7 = /(\d+)-(\d+)-(\d+)/;
const str7 = "Today's date is 2023-10-27";
const newStr2 = str7.replace(regex7, (match, year, month, day) => {
return `${day}/${month}/${year}`;
});
console.log(newStr2); // Saída: Today's date is 27/10/2023
// split()
const regex8 = /, /;
const str8 = "apple, banana, cherry";
const arr = str8.split(regex8);
console.log(arr); // Saída: ["apple", "banana", "cherry"]
Técnicas Avançadas de Regex
Grupos de Captura:
Parênteses () são usados para criar grupos de captura em expressões regulares. Os grupos de captura permitem extrair partes específicas do texto correspondido. Os métodos exec() e match() retornam um array onde o primeiro elemento é a correspondência inteira, e os elementos subsequentes são os grupos capturados.
const regex = /(\d{4})-(\d{2})-(\d{2})/;
const dateString = "2023-10-27";
const match = regex.exec(dateString);
console.log(match[0]); // Saída: 2023-10-27 (A correspondência inteira)
console.log(match[1]); // Saída: 2023 (O primeiro grupo capturado - ano)
console.log(match[2]); // Saída: 10 (O segundo grupo capturado - mês)
console.log(match[3]); // Saída: 27 (O terceiro grupo capturado - dia)
Grupos de Captura Nomeados:
O ES2018 introduziu os grupos de captura nomeados, que permitem atribuir nomes aos grupos de captura usando a sintaxe (?. Isso torna o código mais legível e fácil de manter.
const regex = /(?\d{4})-(?\d{2})-(?\d{2})/;
const dateString = "2023-10-27";
const match = regex.exec(dateString);
console.log(match.groups.year); // Saída: 2023
console.log(match.groups.month); // Saída: 10
console.log(match.groups.day); // Saída: 27
Grupos de Não-Captura:
Se você precisar agrupar partes de uma regex sem capturá-las (por exemplo, para aplicar um quantificador a um grupo), pode usar um grupo de não-captura com a sintaxe (?:...). Isso evita a alocação desnecessária de memória para grupos capturados.
const regex = /(?:https?:\/\/)?([\w\.]+)/; // Corresponde a uma URL, mas captura apenas o nome do domínio
const url = "https://www.example.com/path";
const match = regex.exec(url);
console.log(match[1]); // Saída: www.example.com
Lookarounds (verificações):
Lookarounds são asserções de largura zero que correspondem a uma posição em uma string com base em um padrão que precede (lookbehind) ou segue (lookahead) essa posição, sem incluir o padrão do lookaround na correspondência em si.
- Lookahead Positivo:
(?=...)Corresponde se o padrão dentro do lookahead *segue* a posição atual. - Lookahead Negativo:
(?!...)Corresponde se o padrão dentro do lookahead *não segue* a posição atual. - Lookbehind Positivo:
(?<=...)Corresponde se o padrão dentro do lookbehind *precede* a posição atual. - Lookbehind Negativo:
(? Corresponde se o padrão dentro do lookbehind *não precede* a posição atual.
Exemplo:
// Lookahead Positivo: Obter o preço apenas quando seguido por USD
const regex = /\d+(?= USD)/;
const text = "The price is 100 USD";
const match = text.match(regex);
console.log(match); // Saída: ["100"]
// Lookahead Negativo: Obter a palavra apenas quando não seguida por um número
const regex2 = /\b\w+\b(?! \d)/;
const text2 = "apple 123 banana orange 456";
const matches = text2.match(regex2);
console.log(matches); // Saída: null porque match() retorna apenas a primeira correspondência sem a flag 'g', o que não é o que precisamos.
// para corrigir:
const regex3 = /\b\w+\b(?! \d)/g;
const text3 = "apple 123 banana orange 456";
const matches3 = text3.match(regex3);
console.log(matches3); // Saída: [ 'banana' ]
// Lookbehind Positivo: Obter o valor apenas quando precedido por $
const regex4 = /(?<=\$)\d+/;
const text4 = "The price is $200";
const match4 = text4.match(regex4);
console.log(match4); // Saída: ["200"]
// Lookbehind Negativo: Obter a palavra apenas quando não precedida pela palavra 'not'
const regex5 = /(?
Retrovisores (Backreferences):
Retrovisores permitem que você se refira a grupos capturados anteriormente dentro da mesma expressão regular. Eles usam a sintaxe \1, \2, etc., onde o número corresponde ao número do grupo capturado.
const regex = /([a-z]+) \1/;
const text = "hello hello world";
const match = regex.exec(text);
console.log(match); // Saída: ["hello hello", "hello", index: 0, input: "hello hello world", groups: undefined]
Aplicações Práticas de Expressões Regulares
Validação de Endereços de E-mail:
Um caso de uso comum para expressões regulares é a validação de endereços de e-mail. Embora uma regex perfeita para validação de e-mail seja extremamente complexa, aqui está um exemplo simplificado:
const emailRegex = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/;
console.log(emailRegex.test("test@example.com")); // Saída: true
console.log(emailRegex.test("invalid-email")); // Saída: false
console.log(emailRegex.test("test@sub.example.co.uk")); // Saída: true
Extraindo URLs de um Texto:
Você pode usar expressões regulares para extrair URLs de um bloco de texto:
const urlRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g;
const text = "Visit our website at https://www.example.com or check out http://blog.example.org.";
const urls = text.match(urlRegex);
console.log(urls); // Saída: ["https://www.example.com", "http://blog.example.org"]
Analisando Dados CSV:
Expressões regulares podem ser usadas para analisar dados CSV (Valores Separados por Vírgula). Aqui está um exemplo de como dividir uma string CSV em um array de valores, lidando com campos entre aspas:
const csvString = 'John,Doe,"123, Main St",New York';
const csvRegex = /(?:"([^"]*(?:""[^"]*)*)")|([^,]+)/g; //Regex CSV corrigida
let values = [];
let match;
while (match = csvRegex.exec(csvString)) {
values.push(match[1] ? match[1].replace(/""/g, '"') : match[2]);
}
console.log(values); // Saída: ["John", "Doe", "123, Main St", "New York"]
Validação de Número de Telefone Internacional
A validação de números de telefone internacionais é complexa devido aos diferentes formatos e comprimentos. Uma solução robusta geralmente envolve o uso de uma biblioteca, mas uma regex simplificada pode fornecer uma validação básica:
const phoneRegex = /^\+(?:[0-9] ?){6,14}[0-9]$/;
console.log(phoneRegex.test("+1 555 123 4567")); // Saída: true (Exemplo dos EUA)
console.log(phoneRegex.test("+44 20 7946 0500")); // Saída: true (Exemplo do Reino Unido)
console.log(phoneRegex.test("+81 3 3224 5000")); // Saída: true (Exemplo do Japão)
console.log(phoneRegex.test("123-456-7890")); // Saída: false
Validação de Força de Senha
Expressões regulares são úteis para impor políticas de força de senha. O exemplo abaixo verifica o comprimento mínimo, letra maiúscula, letra minúscula e um número.
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$/;
console.log(passwordRegex.test("P@ssword123")); // Saída: true
console.log(passwordRegex.test("password")); // Saída: false (sem maiúscula ou número)
console.log(passwordRegex.test("Password")); // Saída: false (sem número)
console.log(passwordRegex.test("Pass123")); // Saída: false (sem minúscula)
console.log(passwordRegex.test("P@ss1")); // Saída: false (menos de 8 caracteres)
Técnicas de Otimização de Regex
Expressões regulares podem ser computacionalmente caras, especialmente para padrões complexos ou entradas grandes. Aqui estão algumas técnicas para otimizar o desempenho da regex:
- Seja Específico: Evite usar padrões excessivamente gerais que possam corresponder a mais do que o pretendido.
- Use Âncoras: Ancore a regex ao início ou fim da string sempre que possível (
^,$). - Evite Backtracking: Minimize o backtracking usando quantificadores possessivos (ex:
++em vez de+) ou grupos atômicos ((?>...)) quando apropriado. - Compile Uma Vez: Se você usar a mesma regex várias vezes, compile-a uma vez e reutilize o objeto
RegExp. - Use Classes de Caracteres com Sabedoria: Classes de caracteres (
[]) são geralmente mais rápidas que alternâncias (|). - Mantenha Simples: Evite regexes excessivamente complexas que são difíceis de entender e manter. Às vezes, dividir uma tarefa complexa em várias regexes mais simples ou usar outras técnicas de manipulação de string pode ser mais eficiente.
Erros Comuns em Regex
- Esquecer de Escapar Metacaracteres: Não escapar caracteres especiais como
.,*,+,?,$,^,(,),[,],{,},|, e\quando você quer correspondê-los literalmente. - Uso Excessivo de
.(ponto): O ponto corresponde a qualquer caractere (exceto nova linha em alguns modos), o que pode levar a correspondências inesperadas se não for usado com cuidado. Seja mais específico quando possível, usando classes de caracteres ou outros padrões mais restritivos. - Ganância (Greediness): Por padrão, quantificadores como
*e+são "gulosos" (greedy) e corresponderão o máximo possível. Use quantificadores "preguiçosos" (lazy) (*?,+?) quando precisar corresponder à string mais curta possível. - Uso Incorreto de Âncoras: Entender mal o comportamento de
^(início da string/linha) e$(fim da string/linha) pode levar a correspondências incorretas. Lembre-se de usar a flagm(multilinha) ao trabalhar com strings de várias linhas e desejar que^e$correspondam ao início e fim de cada linha. - Não Lidar com Casos Extremos (Edge Cases): Não considerar todos os cenários de entrada possíveis e casos extremos pode levar a bugs. Teste suas regexes exaustivamente com uma variedade de entradas, incluindo strings vazias, caracteres inválidos e condições de limite.
- Problemas de Desempenho: Construir regexes excessivamente complexas e ineficientes pode causar problemas de desempenho, especialmente com grandes entradas. Otimize suas regexes usando padrões mais específicos, evitando backtracking desnecessário e compilando regexes que são usadas repetidamente.
- Ignorar a Codificação de Caracteres: Não lidar adequadamente com as codificações de caracteres (especialmente Unicode) pode levar a resultados inesperados. Use a flag
uao trabalhar com caracteres Unicode para garantir a correspondência correta.
Conclusão
Expressões regulares são uma ferramenta valiosa para correspondência de padrões e manipulação de texto em JavaScript. Dominar a sintaxe e as técnicas de regex permite que você resolva eficientemente uma ampla gama de problemas, desde a validação de dados até o processamento complexo de texto. Ao entender os conceitos discutidos neste guia e praticar com exemplos do mundo real, você pode se tornar proficiente no uso de expressões regulares para aprimorar suas habilidades de desenvolvimento em JavaScript.
Lembre-se de que as expressões regulares podem ser complexas, e muitas vezes é útil testá-las exaustivamente usando testadores de regex online como regex101.com ou regexr.com. Isso permite que você visualize as correspondências e depure quaisquer problemas de forma eficaz. Bom código!